@brett_lamy/docstream 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +25 -5
  2. package/dist/assets.d.ts +3 -0
  3. package/dist/assets.d.ts.map +1 -0
  4. package/dist/assets.js +20 -0
  5. package/dist/assets.js.map +1 -0
  6. package/dist/demo/DemoViewer.d.ts +64 -0
  7. package/dist/demo/DemoViewer.d.ts.map +1 -0
  8. package/dist/demo/DemoViewer.js +426 -0
  9. package/dist/demo/DemoViewer.js.map +1 -0
  10. package/dist/demo/context.d.ts +14 -0
  11. package/dist/demo/context.d.ts.map +1 -0
  12. package/dist/demo/context.js +25 -0
  13. package/dist/demo/context.js.map +1 -0
  14. package/dist/demo/glob.d.ts +60 -0
  15. package/dist/demo/glob.d.ts.map +1 -0
  16. package/dist/demo/glob.js +158 -0
  17. package/dist/demo/glob.js.map +1 -0
  18. package/dist/demo/index.d.ts +10 -0
  19. package/dist/demo/index.d.ts.map +1 -0
  20. package/dist/demo/index.js +6 -0
  21. package/dist/demo/index.js.map +1 -0
  22. package/dist/demo/markdown.d.ts +60 -0
  23. package/dist/demo/markdown.d.ts.map +1 -0
  24. package/dist/demo/markdown.js +181 -0
  25. package/dist/demo/markdown.js.map +1 -0
  26. package/dist/demo/surface.d.ts +11 -0
  27. package/dist/demo/surface.d.ts.map +1 -0
  28. package/dist/demo/surface.js +69 -0
  29. package/dist/demo/surface.js.map +1 -0
  30. package/dist/demo/types.d.ts +91 -0
  31. package/dist/demo/types.d.ts.map +1 -0
  32. package/dist/demo/types.js +2 -0
  33. package/dist/demo/types.js.map +1 -0
  34. package/dist/docs/CollapsibleCode.d.ts +14 -0
  35. package/dist/docs/CollapsibleCode.d.ts.map +1 -0
  36. package/dist/docs/CollapsibleCode.js +19 -0
  37. package/dist/docs/CollapsibleCode.js.map +1 -0
  38. package/dist/docs/DocsRenderer.d.ts +49 -0
  39. package/dist/docs/DocsRenderer.d.ts.map +1 -0
  40. package/dist/docs/DocsRenderer.js +277 -0
  41. package/dist/docs/DocsRenderer.js.map +1 -0
  42. package/dist/docs/HighlightedCode.d.ts +6 -0
  43. package/dist/docs/HighlightedCode.d.ts.map +1 -0
  44. package/dist/docs/HighlightedCode.js +40 -0
  45. package/dist/docs/HighlightedCode.js.map +1 -0
  46. package/dist/docs/Mermaid.d.ts +7 -0
  47. package/dist/docs/Mermaid.d.ts.map +1 -0
  48. package/dist/docs/Mermaid.js +39 -0
  49. package/dist/docs/Mermaid.js.map +1 -0
  50. package/dist/docs/PageActions.d.ts +33 -0
  51. package/dist/docs/PageActions.d.ts.map +1 -0
  52. package/dist/docs/PageActions.js +115 -0
  53. package/dist/docs/PageActions.js.map +1 -0
  54. package/dist/docs/controls.d.ts +29 -0
  55. package/dist/docs/controls.d.ts.map +1 -0
  56. package/dist/docs/controls.js +46 -0
  57. package/dist/docs/controls.js.map +1 -0
  58. package/dist/docs/copy.d.ts +24 -0
  59. package/dist/docs/copy.d.ts.map +1 -0
  60. package/dist/docs/copy.js +62 -0
  61. package/dist/docs/copy.js.map +1 -0
  62. package/dist/docs/math.d.ts +3 -0
  63. package/dist/docs/math.d.ts.map +1 -0
  64. package/dist/docs/math.js +12 -0
  65. package/dist/docs/math.js.map +1 -0
  66. package/dist/docs/reference.d.ts +27 -0
  67. package/dist/docs/reference.d.ts.map +1 -0
  68. package/dist/docs/reference.js +73 -0
  69. package/dist/docs/reference.js.map +1 -0
  70. package/dist/docs/tabs-sync.d.ts +5 -0
  71. package/dist/docs/tabs-sync.d.ts.map +1 -0
  72. package/dist/docs/tabs-sync.js +58 -0
  73. package/dist/docs/tabs-sync.js.map +1 -0
  74. package/dist/gitbook/ast.d.ts +506 -0
  75. package/dist/gitbook/ast.d.ts.map +1 -0
  76. package/dist/gitbook/ast.js +8 -0
  77. package/dist/gitbook/ast.js.map +1 -0
  78. package/dist/gitbook/attrs.d.ts +14 -0
  79. package/dist/gitbook/attrs.d.ts.map +1 -0
  80. package/dist/gitbook/attrs.js +36 -0
  81. package/dist/gitbook/attrs.js.map +1 -0
  82. package/dist/gitbook/flatten.d.ts +14 -0
  83. package/dist/gitbook/flatten.d.ts.map +1 -0
  84. package/dist/gitbook/flatten.js +45 -0
  85. package/dist/gitbook/flatten.js.map +1 -0
  86. package/dist/gitbook/index.d.ts +10 -0
  87. package/dist/gitbook/index.d.ts.map +1 -0
  88. package/dist/gitbook/index.js +8 -0
  89. package/dist/gitbook/index.js.map +1 -0
  90. package/dist/gitbook/inline.d.ts +28 -0
  91. package/dist/gitbook/inline.d.ts.map +1 -0
  92. package/dist/gitbook/inline.js +956 -0
  93. package/dist/gitbook/inline.js.map +1 -0
  94. package/dist/gitbook/outline.d.ts +16 -0
  95. package/dist/gitbook/outline.d.ts.map +1 -0
  96. package/dist/gitbook/outline.js +44 -0
  97. package/dist/gitbook/outline.js.map +1 -0
  98. package/dist/gitbook/package-managers.d.ts +12 -0
  99. package/dist/gitbook/package-managers.d.ts.map +1 -0
  100. package/dist/gitbook/package-managers.js +93 -0
  101. package/dist/gitbook/package-managers.js.map +1 -0
  102. package/dist/gitbook/parse.d.ts +44 -0
  103. package/dist/gitbook/parse.d.ts.map +1 -0
  104. package/dist/gitbook/parse.js +1113 -0
  105. package/dist/gitbook/parse.js.map +1 -0
  106. package/dist/gitbook/serialize.d.ts +23 -0
  107. package/dist/gitbook/serialize.d.ts.map +1 -0
  108. package/dist/gitbook/serialize.js +498 -0
  109. package/dist/gitbook/serialize.js.map +1 -0
  110. package/dist/index.d.ts +34 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.js +24 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/openapi/OpenApiOperation.d.ts +6 -0
  115. package/dist/openapi/OpenApiOperation.d.ts.map +1 -0
  116. package/dist/openapi/OpenApiOperation.js +112 -0
  117. package/dist/openapi/OpenApiOperation.js.map +1 -0
  118. package/dist/playground/InlineDemoPreview.d.ts +24 -0
  119. package/dist/playground/InlineDemoPreview.d.ts.map +1 -0
  120. package/dist/playground/InlineDemoPreview.js +48 -0
  121. package/dist/playground/InlineDemoPreview.js.map +1 -0
  122. package/dist/playground/PlaygroundStreamdown.d.ts +16 -0
  123. package/dist/playground/PlaygroundStreamdown.d.ts.map +1 -0
  124. package/dist/playground/PlaygroundStreamdown.js +22 -0
  125. package/dist/playground/PlaygroundStreamdown.js.map +1 -0
  126. package/dist/playground/ReactCodePreview.d.ts +64 -0
  127. package/dist/playground/ReactCodePreview.d.ts.map +1 -0
  128. package/dist/playground/ReactCodePreview.js +108 -0
  129. package/dist/playground/ReactCodePreview.js.map +1 -0
  130. package/dist/playground/filesystem.d.ts +27 -0
  131. package/dist/playground/filesystem.d.ts.map +1 -0
  132. package/dist/playground/filesystem.js +65 -0
  133. package/dist/playground/filesystem.js.map +1 -0
  134. package/dist/playground/index.d.ts +9 -0
  135. package/dist/playground/index.d.ts.map +1 -0
  136. package/dist/playground/index.js +7 -0
  137. package/dist/playground/index.js.map +1 -0
  138. package/dist/playground/project.d.ts +57 -0
  139. package/dist/playground/project.d.ts.map +1 -0
  140. package/dist/playground/project.js +137 -0
  141. package/dist/playground/project.js.map +1 -0
  142. package/dist/replay/NativeReplay.d.ts +9 -0
  143. package/dist/replay/NativeReplay.d.ts.map +1 -0
  144. package/dist/replay/NativeReplay.js +173 -0
  145. package/dist/replay/NativeReplay.js.map +1 -0
  146. package/dist/replay/ReplayPreview.d.ts +33 -0
  147. package/dist/replay/ReplayPreview.d.ts.map +1 -0
  148. package/dist/replay/ReplayPreview.js +91 -0
  149. package/dist/replay/ReplayPreview.js.map +1 -0
  150. package/dist/replay/index.d.ts +5 -0
  151. package/dist/replay/index.d.ts.map +1 -0
  152. package/dist/replay/index.js +4 -0
  153. package/dist/replay/index.js.map +1 -0
  154. package/dist/replay/sanitize.d.ts +4 -0
  155. package/dist/replay/sanitize.d.ts.map +1 -0
  156. package/dist/replay/sanitize.js +86 -0
  157. package/dist/replay/sanitize.js.map +1 -0
  158. package/dist/replay/url.d.ts +8 -0
  159. package/dist/replay/url.d.ts.map +1 -0
  160. package/dist/replay/url.js +41 -0
  161. package/dist/replay/url.js.map +1 -0
  162. package/dist/source/SourcePreview.d.ts +13 -0
  163. package/dist/source/SourcePreview.d.ts.map +1 -0
  164. package/dist/source/SourcePreview.js +47 -0
  165. package/dist/source/SourcePreview.js.map +1 -0
  166. package/dist/source/client.d.ts +7 -0
  167. package/dist/source/client.d.ts.map +1 -0
  168. package/dist/source/client.js +52 -0
  169. package/dist/source/client.js.map +1 -0
  170. package/dist/source/index.d.ts +6 -0
  171. package/dist/source/index.d.ts.map +1 -0
  172. package/dist/source/index.js +3 -0
  173. package/dist/source/index.js.map +1 -0
  174. package/dist/source/types.d.ts +33 -0
  175. package/dist/source/types.d.ts.map +1 -0
  176. package/dist/source/types.js +2 -0
  177. package/dist/source/types.js.map +1 -0
  178. package/dist/streamdown.d.ts +19 -0
  179. package/dist/streamdown.d.ts.map +1 -0
  180. package/dist/streamdown.js +15 -0
  181. package/dist/streamdown.js.map +1 -0
  182. package/dist/styles.css +2441 -0
  183. package/dist/video/VideoEmbed.d.ts +17 -0
  184. package/dist/video/VideoEmbed.d.ts.map +1 -0
  185. package/dist/video/VideoEmbed.js +12 -0
  186. package/dist/video/VideoEmbed.js.map +1 -0
  187. package/dist/video/index.d.ts +3 -0
  188. package/dist/video/index.d.ts.map +1 -0
  189. package/dist/video/index.js +2 -0
  190. package/dist/video/index.js.map +1 -0
  191. package/dist/vite/index.d.ts +21 -0
  192. package/dist/vite/index.d.ts.map +1 -0
  193. package/dist/vite/index.js +137 -0
  194. package/dist/vite/index.js.map +1 -0
  195. package/dist/viz/VizEmbed.d.ts +18 -0
  196. package/dist/viz/VizEmbed.d.ts.map +1 -0
  197. package/dist/viz/VizEmbed.js +12 -0
  198. package/dist/viz/VizEmbed.js.map +1 -0
  199. package/dist/viz/index.d.ts +4 -0
  200. package/dist/viz/index.d.ts.map +1 -0
  201. package/dist/viz/index.js +3 -0
  202. package/dist/viz/index.js.map +1 -0
  203. package/package.json +62 -32
  204. package/src/docs/Mermaid.tsx +5 -3
  205. package/src/replay/ReplayPreview.tsx +0 -1
  206. package/src/styles.css +91 -0
  207. package/src/replay/styles.css +0 -87
package/README.md CHANGED
@@ -632,21 +632,41 @@ import { ReactDemo } from "@brett_lamy/docstream/playground"
632
632
 
633
633
  ## Bundler Notes
634
634
 
635
- This release ships TypeScript and TSX source through ESM exports:
635
+ Since 1.4 the package ships compiled ES modules (ES2022, `react-jsx`) with type
636
+ declarations and declaration maps — one file per source module in `dist/`,
637
+ dependencies left as imports, lazy features (Mermaid, KaTeX, rrweb, the
638
+ playground's almost-node) still behind dynamic `import()`:
636
639
 
637
640
  ```json
638
641
  {
639
642
  "exports": {
640
643
  ".": {
641
- "types": "./src/index.ts",
642
- "import": "./src/index.ts"
644
+ "source": "./src/index.ts",
645
+ "types": "./dist/index.d.ts",
646
+ "import": "./dist/index.js",
647
+ "default": "./dist/index.js"
643
648
  },
644
- "./styles.css": "./src/styles.css"
649
+ "./styles.css": "./dist/styles.css"
645
650
  }
646
651
  }
647
652
  ```
648
653
 
649
- It is validated with Vite and modern TypeScript `moduleResolution: "Bundler"`. Plain Node.js, CommonJS, or tooling that does not transpile TypeScript in dependencies may need a future precompiled JS build.
654
+ No bundler configuration is needed: Vite pre-bundles docstream like any other
655
+ dependency (no `optimizeDeps` entries), Next.js and webpack consume it as plain
656
+ ESM, and your type-check reads `.d.ts` files instead of docstream's sources. The
657
+ `@brett_lamy/docstream/vite` entry is a Node-only Vite plugin; import it from
658
+ `vite.config.ts`, never from browser code.
659
+
660
+ The TypeScript sources are published too, behind a `source` export condition, for
661
+ monorepos that want to compile docstream from source (e.g. Vite
662
+ `resolve.conditions: ["source", ...]` or TypeScript `customConditions`).
663
+
664
+ ## Migrating to 1.4
665
+
666
+ No API changes. Exports now resolve to `dist/*.js` + `dist/*.d.ts` instead of
667
+ `src/*.ts(x)`; drop any `optimizeDeps.include` / `exclude` or
668
+ `transpilePackages` entries you added for docstream, and any `lib` / `types`
669
+ settings you added only so your type-check could compile docstream's sources.
650
670
 
651
671
  ## Migrating to 1.3
652
672
 
@@ -0,0 +1,3 @@
1
+ export declare function setAssetBase(repo: string | null, branch: string | null, filePath: string | null): void;
2
+ export declare function resolveAsset(src: string): string;
3
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,QAO/F;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD"}
package/dist/assets.js ADDED
@@ -0,0 +1,20 @@
1
+ // Resolves repo-relative image paths (e.g. "readme-assets/logo.svg") against
2
+ // the raw.githubusercontent.com URL of the file being edited. The AST keeps
3
+ // the original relative src; only rendering resolves it.
4
+ let assetBase = null;
5
+ export function setAssetBase(repo, branch, filePath) {
6
+ if (!repo || !branch || !filePath) {
7
+ assetBase = null;
8
+ return;
9
+ }
10
+ const dir = filePath.includes("/") ? filePath.slice(0, filePath.lastIndexOf("/") + 1) : "";
11
+ assetBase = `https://raw.githubusercontent.com/${repo}/${branch}/${dir}`;
12
+ }
13
+ export function resolveAsset(src) {
14
+ if (!src || /^(https?:|data:|blob:)/i.test(src))
15
+ return src;
16
+ if (!assetBase)
17
+ return src;
18
+ return assetBase + src.replace(/^\.\//, "");
19
+ }
20
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,4EAA4E;AAC5E,yDAAyD;AAEzD,IAAI,SAAS,GAAkB,IAAI,CAAA;AAEnC,MAAM,UAAU,YAAY,CAAC,IAAmB,EAAE,MAAqB,EAAE,QAAuB;IAC9F,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,SAAS,GAAG,IAAI,CAAA;QAChB,OAAM;IACR,CAAC;IACD,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC1F,SAAS,GAAG,qCAAqC,IAAI,IAAI,MAAM,IAAI,GAAG,EAAE,CAAA;AAC1E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC,GAAG,IAAI,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAA;IAC3D,IAAI,CAAC,SAAS;QAAE,OAAO,GAAG,CAAA;IAC1B,OAAO,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;AAC7C,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { type CSSProperties } from "react";
2
+ import type { DemoLayout, DemoNode, DemoViewport } from "../gitbook/ast.js";
3
+ import type { DemoFile, DemoResolver, DemoVariant, InlineDemoRuntime } from "./types.js";
4
+ export interface DemoViewerProps {
5
+ /** `<page>/<example>` id handed to the resolver. */
6
+ src: string;
7
+ /** Defaults to the nearest `demoResolver` provided to a renderer. */
8
+ resolver?: DemoResolver;
9
+ title?: string;
10
+ description?: string;
11
+ /** Preview height (px number, digits, or any CSS length). */
12
+ height?: number | string;
13
+ layout?: DemoLayout;
14
+ variants?: DemoVariant[];
15
+ /** Initial viewport of the multi-file preview. */
16
+ viewport?: DemoViewport;
17
+ /**
18
+ * Files carried inline by the block (entry first unless `entry` says otherwise). Used when
19
+ * no resolver knows `src`, or the resolver fails for it.
20
+ */
21
+ files?: DemoFile[];
22
+ /** Entry among `files`. Defaults to the first file. */
23
+ entry?: string;
24
+ /** The block is still streaming in: show its code, but don't run it yet. */
25
+ streaming?: boolean;
26
+ /** Runs inline files in the Preview. Defaults to the runtime in context (`demoRuntime`). */
27
+ runtime?: InlineDemoRuntime;
28
+ /** Extra npm dependencies for the runtime, merged over `demoDependencies` from context. */
29
+ dependencies?: Record<string, string>;
30
+ /** Class on the viewer's outer element. (The preview canvas's class is `canvasClassName`.) */
31
+ className?: string;
32
+ /** Status shown in the header (`meta.status`). */
33
+ status?: string;
34
+ /** Full-bleed canvas without the padded surface (`meta.bleed`). */
35
+ bleed?: boolean;
36
+ /** Extra class on the preview canvas (`meta.className`). */
37
+ canvasClassName?: string;
38
+ /** Canvas inline style: an object like `meta.surface`, or CSS declarations as in the tag's `surface`. */
39
+ surface?: Record<string, string | number> | string;
40
+ /** Width of the variant switch (`meta.variantsWidth`). */
41
+ variantsWidth?: number | string;
42
+ }
43
+ /**
44
+ * Resolve demos ahead of rendering (meta, files and component), so the first render — including
45
+ * server rendering and static export — shows them resolved instead of loading. Failures are
46
+ * remembered too, so inline-file fallbacks apply immediately.
47
+ */
48
+ export declare function preloadDemos(resolver: DemoResolver, srcs: string[]): Promise<void>;
49
+ export declare const NO_RUNTIME_NOTE = "Live preview needs the playground runtime. The code is shown instead.";
50
+ export declare function DemoViewer(props: DemoViewerProps): import("react").JSX.Element;
51
+ /** Renders a parsed `{% demo %}` block with the resolver and runtime from context. */
52
+ export declare function DemoBlock({ node }: {
53
+ node: DemoNode;
54
+ }): import("react").JSX.Element;
55
+ export interface DemoFullscreenProps {
56
+ src: string;
57
+ resolver?: DemoResolver;
58
+ variant?: string;
59
+ className?: string;
60
+ style?: CSSProperties;
61
+ }
62
+ /** One demo filling the viewport, for a `?demo=<src>` deep link the host routes. */
63
+ export declare function DemoFullscreen({ src, resolver: override, variant, className, style }: DemoFullscreenProps): import("react").JSX.Element;
64
+ //# sourceMappingURL=DemoViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoViewer.d.ts","sourceRoot":"","sources":["../../src/demo/DemoViewer.tsx"],"names":[],"mappings":"AAcA,OAAO,EAQL,KAAK,aAAa,EAKnB,MAAM,OAAO,CAAA;AAId,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAUxE,OAAO,KAAK,EAAiB,QAAQ,EAAY,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAE9G,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,2FAA2F;IAC3F,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,8FAA8F;IAC9F,SAAS,CAAC,EAAE,MAAM,CAAA;IAMlB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,yGAAyG;IACzG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAAA;IAClD,0DAA0D;IAC1D,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAChC;AAaD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AA2HD,eAAO,MAAM,eAAe,0EAA0E,CAAA;AA4VtG,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,+BA8KhD;AAyGD,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,+BAoBrD;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,aAAa,CAAA;CACtB;AAED,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,mBAAmB,+BAYzG"}
@@ -0,0 +1,426 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /* The demo viewer for `{% demo src="<page>/<example>" %}`.
3
+
4
+ Where the files come from:
5
+ - a host `DemoResolver` that knows `src`: the component renders in-page; the Code view shows
6
+ the resolver's files (they win over any inline copy);
7
+ - otherwise the block's inline files (`{% demo %}` + fences + `{% enddemo %}`): the Preview
8
+ runs them through the `InlineDemoRuntime` in context (almost-node in the playground entry),
9
+ or, without one, the viewer opens on the Code view with a short note — never an empty box.
10
+
11
+ Multi-file demos get the gallery viewer: a header (title, description, Preview | Code, variants,
12
+ viewport widths, copy, fullscreen), a resizable preview frame on a dotted stage, and a Code view
13
+ with a file tree and highlighted source. Single-file demos keep the ReactDemo card: the preview,
14
+ then a collapsible, line-numbered code panel. Either way the component renders in-page (lazy
15
+ `load()`, mounted once the card nears the viewport) and the code shown is the real file. */
16
+ import { Component, createElement, useEffect, useId, useMemo, useRef, useState, } from "react";
17
+ import { motion, useReducedMotion } from "framer-motion";
18
+ import { ExternalLink, FileCode2, Folder, Maximize2, Monitor, Smartphone, Tablet, X } from "lucide-react";
19
+ import { parseInline } from "../gitbook/inline.js";
20
+ import { CollapsibleCode } from "../docs/CollapsibleCode.js";
21
+ import { HighlightedCode } from "../docs/HighlightedCode.js";
22
+ import { CopyButton } from "../docs/copy.js";
23
+ import { PillTabs, Segmented, rovingKeyDown } from "../docs/controls.js";
24
+ import { useDemoResolver, useDemoRuntime } from "./context.js";
25
+ import { languageForPath } from "./glob.js";
26
+ import { inlineDemoMarkdown } from "./markdown.js";
27
+ import { cssToSurface, surfaceStyle, surfaceToCss } from "./surface.js";
28
+ const settled = new WeakMap();
29
+ function cacheFor(resolver) {
30
+ let cache = settled.get(resolver);
31
+ if (!cache)
32
+ settled.set(resolver, (cache = new Map()));
33
+ return cache;
34
+ }
35
+ /**
36
+ * Resolve demos ahead of rendering (meta, files and component), so the first render — including
37
+ * server rendering and static export — shows them resolved instead of loading. Failures are
38
+ * remembered too, so inline-file fallbacks apply immediately.
39
+ */
40
+ export async function preloadDemos(resolver, srcs) {
41
+ const cache = cacheFor(resolver);
42
+ const settle = async (key, run) => {
43
+ try {
44
+ cache.set(key, { status: "ready", value: await run() });
45
+ }
46
+ catch (cause) {
47
+ cache.set(key, { status: "error", error: cause instanceof Error ? cause : new Error(String(cause)) });
48
+ }
49
+ };
50
+ await Promise.all(srcs.flatMap((src) => [
51
+ settle(`meta:${src}`, () => resolver.meta(src)),
52
+ settle(`files:${src}`, () => resolver.files(src)),
53
+ settle(`load:${src}`, () => resolver.load(src)),
54
+ ]));
55
+ }
56
+ /** Resolves `run()` once `active`, remembering settled values so remounts don't flash. */
57
+ function useResolved(resolver, key, active, run) {
58
+ const cached = resolver ? cacheFor(resolver).get(key) : undefined;
59
+ const [state, setState] = useState(cached ?? { status: "loading" });
60
+ useEffect(() => {
61
+ if (!resolver)
62
+ return;
63
+ const hit = cacheFor(resolver).get(key);
64
+ if (hit && hit.status !== "loading") {
65
+ setState(hit);
66
+ return;
67
+ }
68
+ if (!active) {
69
+ setState({ status: "loading" });
70
+ return;
71
+ }
72
+ let live = true;
73
+ run().then((value) => {
74
+ const next = { status: "ready", value };
75
+ cacheFor(resolver).set(key, next);
76
+ if (live)
77
+ setState(next);
78
+ }, (cause) => {
79
+ const error = cause instanceof Error ? cause : new Error(String(cause));
80
+ if (live)
81
+ setState({ status: "error", error });
82
+ });
83
+ return () => {
84
+ live = false;
85
+ };
86
+ // `run` closes over resolver + key only.
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ }, [resolver, key, active]);
89
+ return state;
90
+ }
91
+ const useMeta = (resolver, src) => useResolved(resolver, `meta:${src}`, true, () => resolver.meta(src));
92
+ const useFiles = (resolver, src, active) => useResolved(resolver, `files:${src}`, active, () => resolver.files(src));
93
+ const useComponent = (resolver, src, active) => useResolved(resolver, `load:${src}`, active, () => resolver.load(src));
94
+ /** True once the element has come within `margin` of the viewport (stays true). */
95
+ function useNearViewport(margin = "400px 0px") {
96
+ const ref = useRef(null);
97
+ const [near, setNear] = useState(false);
98
+ useEffect(() => {
99
+ const el = ref.current;
100
+ if (!el || near)
101
+ return;
102
+ if (typeof IntersectionObserver === "undefined") {
103
+ setNear(true);
104
+ return;
105
+ }
106
+ const observer = new IntersectionObserver(([entry]) => {
107
+ if (entry?.isIntersecting)
108
+ setNear(true);
109
+ }, { rootMargin: margin });
110
+ observer.observe(el);
111
+ return () => observer.disconnect();
112
+ }, [near, margin]);
113
+ return [ref, near];
114
+ }
115
+ /* ---------- pieces ---------- */
116
+ class DemoErrorBoundary extends Component {
117
+ state = { error: null };
118
+ static getDerivedStateFromError(error) {
119
+ return { error: error instanceof Error ? error : new Error(String(error)) };
120
+ }
121
+ componentDidUpdate(prev) {
122
+ if (prev.src !== this.props.src && this.state.error)
123
+ this.setState({ error: null });
124
+ }
125
+ render() {
126
+ return this.state.error ? _jsx(DemoError, { message: `${this.props.src} failed to render: ${this.state.error.message}` }) : this.props.children;
127
+ }
128
+ }
129
+ function DemoError({ message }) {
130
+ return _jsx("pre", { className: "docs-demo-error", role: "alert", children: message });
131
+ }
132
+ function Spinner({ label = "Loading demo" }) {
133
+ return (_jsx("div", { className: "docs-demo-loading", role: "status", "aria-label": label, children: _jsx("span", { className: "docs-demo-spinner", "aria-hidden": "true" }) }));
134
+ }
135
+ /** Descriptions allow inline Markdown (`code`, **bold**, _em_, links). */
136
+ function InlineMarkdown({ text }) {
137
+ const nodes = useMemo(() => parseInline(text), [text]);
138
+ return (_jsx(_Fragment, { children: nodes.map((n, i) => {
139
+ if (n.type !== "text")
140
+ return null;
141
+ let el = n.text;
142
+ if (n.code)
143
+ el = _jsx("code", { children: el });
144
+ if (n.bold)
145
+ el = _jsx("strong", { children: el });
146
+ if (n.italic)
147
+ el = _jsx("em", { children: el });
148
+ if (n.link)
149
+ el = _jsx("a", { href: n.link, children: el });
150
+ return _jsx("span", { children: el }, i);
151
+ }) }));
152
+ }
153
+ function cssLength(value, fallback) {
154
+ if (value === undefined || value === "")
155
+ return fallback;
156
+ if (typeof value === "number")
157
+ return `${value}px`;
158
+ return /^\d+(?:\.\d+)?$/.test(value.trim()) ? `${value.trim()}px` : value;
159
+ }
160
+ export const NO_RUNTIME_NOTE = "Live preview needs the playground runtime. The code is shown instead.";
161
+ function DemoCanvas({ src, preview, variant, bleed, look }) {
162
+ if (preview.kind === "unavailable" || preview.kind === "streaming") {
163
+ return (_jsx("div", { className: "docs-demo-note", role: "note", "data-variant": variant, children: preview.kind === "streaming" ? "The demo is still arriving; its preview starts once the block is complete." : NO_RUNTIME_NOTE }));
164
+ }
165
+ if (preview.kind === "runtime") {
166
+ return (_jsx("div", { className: ["docs-demo-canvas docs-demo-canvas-bleed docs-demo-canvas-runtime", look?.className ?? ""].filter(Boolean).join(" "), style: surfaceStyle(look?.surface), "data-variant": variant, children: _jsx(DemoErrorBoundary, { src: src || "demo", children: preview.render(variant) }) }));
167
+ }
168
+ const { component } = preview;
169
+ return (_jsx("div", { className: ["docs-demo-canvas", bleed ? "docs-demo-canvas-bleed" : "", look?.className ?? ""].filter(Boolean).join(" "), style: surfaceStyle(look?.surface), "data-variant": variant, children: component.status === "ready" ? (_jsx(DemoErrorBoundary, { src: src, children: createElement(component.value, variant === undefined ? {} : { variant }) })) : component.status === "error" ? (_jsx(DemoError, { message: `${src} failed to load: ${component.error.message}` })) : (_jsx(Spinner, {})) }));
170
+ }
171
+ const VIEWPORTS = [
172
+ { id: "desktop", label: "Desktop", width: null, Icon: Monitor },
173
+ { id: "tablet", label: "Tablet", width: 768, Icon: Tablet },
174
+ { id: "phone", label: "Phone", width: 390, Icon: Smartphone },
175
+ ];
176
+ const MIN_WIDTH = 280;
177
+ /** The resizable preview frame on its stage. `width === null` fills the stage. */
178
+ function PreviewStage({ width, onResize, height, children }) {
179
+ const reduced = useReducedMotion();
180
+ const frame = useRef(null);
181
+ const [dragging, setDragging] = useState(false);
182
+ const [measured, setMeasured] = useState(null);
183
+ const [max, setMax] = useState(0);
184
+ useEffect(() => {
185
+ const el = frame.current;
186
+ if (!el || typeof ResizeObserver === "undefined")
187
+ return;
188
+ const observer = new ResizeObserver(() => {
189
+ setMeasured(Math.round(el.getBoundingClientRect().width));
190
+ setMax(el.parentElement?.clientWidth ?? 0);
191
+ });
192
+ observer.observe(el);
193
+ if (el.parentElement)
194
+ observer.observe(el.parentElement);
195
+ return () => observer.disconnect();
196
+ }, []);
197
+ const clamp = (next) => {
198
+ const limit = frame.current?.parentElement?.clientWidth ?? next;
199
+ const value = Math.round(Math.min(limit, Math.max(MIN_WIDTH, next)));
200
+ return value >= limit - 4 ? null : value;
201
+ };
202
+ const startDrag = (event) => {
203
+ const el = frame.current;
204
+ if (!el || event.button !== 0)
205
+ return;
206
+ event.preventDefault();
207
+ const left = el.getBoundingClientRect().left;
208
+ setDragging(true);
209
+ const move = (ev) => onResize(clamp(ev.clientX - left));
210
+ const up = () => {
211
+ setDragging(false);
212
+ window.removeEventListener("pointermove", move);
213
+ window.removeEventListener("pointerup", up);
214
+ window.removeEventListener("pointercancel", up);
215
+ };
216
+ window.addEventListener("pointermove", move);
217
+ window.addEventListener("pointerup", up);
218
+ window.addEventListener("pointercancel", up);
219
+ };
220
+ const onKeyDown = (event) => {
221
+ const current = width ?? measured ?? max;
222
+ const step = event.shiftKey ? 50 : 10;
223
+ let next;
224
+ if (event.key === "ArrowLeft")
225
+ next = clamp(current - step);
226
+ else if (event.key === "ArrowRight")
227
+ next = clamp(current + step);
228
+ else if (event.key === "Home")
229
+ next = clamp(MIN_WIDTH);
230
+ else if (event.key === "End")
231
+ next = null;
232
+ if (next === undefined)
233
+ return;
234
+ event.preventDefault();
235
+ onResize(next);
236
+ };
237
+ return (_jsxs("div", { className: "docs-demo-stage", style: { height }, "data-dragging": dragging || undefined, children: [_jsx(motion.div, { ref: frame, className: "docs-demo-frame", "data-sized": width !== null || undefined, initial: false, animate: { width: width ?? "100%" }, transition: dragging || reduced ? { duration: 0 } : { type: "spring", bounce: 0.12, duration: 0.5 }, children: children }), _jsx("div", { className: "docs-demo-handle", role: "separator", tabIndex: 0, "aria-orientation": "vertical", "aria-label": "Resize preview", "aria-valuemin": MIN_WIDTH, "aria-valuemax": max || undefined, "aria-valuenow": measured ?? undefined, "aria-valuetext": measured ? `${measured} pixels wide` : undefined, "data-dragging": dragging || undefined, onPointerDown: startDrag, onKeyDown: onKeyDown, onDoubleClick: () => onResize(null) }), _jsx("span", { className: "docs-demo-readout", "aria-hidden": "true", children: measured ? `${measured}px` : "" })] }));
238
+ }
239
+ /** Files as a tree: the entry and root files first, then each folder with its files indented. */
240
+ function fileTree(files) {
241
+ const rows = [];
242
+ const seen = new Set();
243
+ for (const file of files) {
244
+ const parts = file.path.split("/");
245
+ for (let d = 0; d < parts.length - 1; d++) {
246
+ const dir = parts.slice(0, d + 1).join("/");
247
+ if (!seen.has(dir)) {
248
+ seen.add(dir);
249
+ rows.push({ kind: "dir", name: parts[d], depth: d, path: dir });
250
+ }
251
+ }
252
+ rows.push({ kind: "file", name: parts[parts.length - 1], depth: parts.length - 1, path: file.path });
253
+ }
254
+ return rows;
255
+ }
256
+ function CodeView({ src, files, height, id }) {
257
+ const [active, setActive] = useState(files[0]?.path ?? "");
258
+ useEffect(() => {
259
+ if (!files.some((f) => f.path === active))
260
+ setActive(files[0]?.path ?? "");
261
+ }, [files, active]);
262
+ const file = files.find((f) => f.path === active) ?? files[0];
263
+ const rows = useMemo(() => fileTree(files), [files]);
264
+ const paths = files.map((f) => f.path);
265
+ const tabId = (path) => `${id}-file-${paths.indexOf(path)}`;
266
+ const panelId = `${id}-source`;
267
+ return (_jsxs("div", { className: "docs-demo-code", style: { height }, children: [_jsxs("div", { className: "docs-demo-files", role: "tablist", "aria-orientation": "vertical", "aria-label": "Files", onKeyDown: (event) => rovingKeyDown(event, paths, active, setActive, "vertical"), children: [_jsx("div", { className: "docs-demo-files-h", "aria-hidden": "true", children: "Files" }), rows.map((row) => row.kind === "dir" ? (_jsxs("div", { className: "docs-demo-dir", style: { "--depth": row.depth }, "aria-hidden": "true", children: [_jsx(Folder, { width: 13, height: 13, "aria-hidden": "true" }), row.name] }, `d:${row.path}`)) : (_jsxs("button", { id: tabId(row.path), type: "button", role: "tab", "data-roving": "", "aria-selected": row.path === active, "aria-controls": panelId, tabIndex: row.path === active ? 0 : -1, title: row.path, className: "docs-demo-file", style: { "--depth": row.depth }, onClick: () => setActive(row.path), children: [_jsx(FileCode2, { width: 13, height: 13, "aria-hidden": "true" }), _jsx("span", { children: row.name })] }, row.path)))] }), _jsxs("div", { className: "docs-demo-source", role: "tabpanel", id: panelId, "aria-labelledby": file ? tabId(file.path) : undefined, children: [_jsxs("div", { className: "docs-demo-source-h", children: [_jsx("span", { className: "docs-demo-source-path", children: src && file ? `${src}/${file.path}` : file?.path }), file ? _jsx(CopyButton, { text: file.content, label: `Copy ${file.path}` }) : null] }), _jsx("div", { className: "docs-demo-source-body", tabIndex: 0, "aria-label": file ? `${file.path} source` : undefined, children: file ? (_jsx("pre", { className: "docs-code-numbered", children: _jsx(HighlightedCode, { code: file.content.replace(/\n+$/, ""), language: file.language ?? languageForPath(file.path) ?? null, lineNumbers: true }) })) : (_jsx(Spinner, { label: "Loading files" })) })] })] }));
268
+ }
269
+ function FullscreenButton({ onOpen, title }) {
270
+ return (_jsx("button", { type: "button", className: "docs-demo-icon-btn", onClick: onOpen, "aria-label": `Open ${title} fullscreen`, title: "Fullscreen", children: _jsx(Maximize2, { width: 15, height: 15, "aria-hidden": "true" }) }));
271
+ }
272
+ function NewTabLink({ href, title }) {
273
+ if (!href)
274
+ return null;
275
+ return (_jsx("a", { className: "docs-demo-icon-btn", href: href, target: "_blank", rel: "noreferrer", "aria-label": `Open ${title} in a new tab`, title: "Open in new tab", children: _jsx(ExternalLink, { width: 15, height: 15, "aria-hidden": "true" }) }));
276
+ }
277
+ function VariantSwitch({ variants, value, onChange, title, width }) {
278
+ if (!variants?.length || value === undefined)
279
+ return null;
280
+ return (_jsx(Segmented, { options: variants, value: value, onChange: onChange, label: `${title} variant`, className: width === undefined ? "docs-demo-variants" : "docs-demo-variants docs-demo-variants-sized", style: width === undefined ? undefined : { width: cssLength(width, "auto") } }));
281
+ }
282
+ function StatusBadge({ status }) {
283
+ return status ? _jsx("span", { className: "docs-demo-status", children: status }) : null;
284
+ }
285
+ /** A full-viewport overlay (native modal dialog: top layer, focus trap, Esc to close). */
286
+ function FullscreenDialog({ open, onClose, title, src, variants, variant, onVariant, preview, bleed, look, variantsWidth, href }) {
287
+ const ref = useRef(null);
288
+ const titleId = useId();
289
+ useEffect(() => {
290
+ const dialog = ref.current;
291
+ if (!dialog)
292
+ return;
293
+ if (open && !dialog.open) {
294
+ if (typeof dialog.showModal === "function")
295
+ dialog.showModal();
296
+ else
297
+ dialog.setAttribute("open", "");
298
+ }
299
+ else if (!open && dialog.open) {
300
+ dialog.close();
301
+ }
302
+ }, [open]);
303
+ return (_jsx("dialog", { ref: ref, className: "docs-demo-dialog", "aria-labelledby": titleId, onClose: onClose, onCancel: onClose, children: open ? (_jsxs("div", { className: "docs-demo-dialog-inner", children: [_jsxs("div", { className: "docs-demo-dialog-bar", children: [_jsx("span", { id: titleId, className: "docs-demo-title", children: title }), _jsx("span", { className: "docs-demo-dialog-src", children: src }), _jsx("span", { className: "docs-demo-spacer" }), _jsx(VariantSwitch, { variants: variants, value: variant, onChange: onVariant, title: title, width: variantsWidth }), _jsx(NewTabLink, { href: href, title: title }), _jsx("button", { type: "button", className: "docs-demo-icon-btn", onClick: onClose, "aria-label": "Close fullscreen", title: "Close (Esc)", autoFocus: true, children: _jsx(X, { width: 16, height: 16, "aria-hidden": "true" }) })] }), _jsx("div", { className: "docs-demo-dialog-stage", children: _jsx(DemoCanvas, { src: src, preview: preview, variant: variant, bleed: bleed, look: look }) })] })) : null }));
304
+ }
305
+ /** Inline files, entry first. */
306
+ function orderInline(files, entry) {
307
+ if (!files?.length)
308
+ return undefined;
309
+ const index = entry ? files.findIndex((f) => f.path === entry) : -1;
310
+ return index > 0 ? [files[index], ...files.slice(0, index), ...files.slice(index + 1)] : files;
311
+ }
312
+ export function DemoViewer(props) {
313
+ const resolver = useDemoResolver(props.resolver);
314
+ const runtimeOptions = useDemoRuntime({ runtime: props.runtime, dependencies: props.dependencies });
315
+ const { src } = props;
316
+ const [ref, near] = useNearViewport();
317
+ const inlineFiles = useMemo(() => orderInline(props.files, props.entry), [props.files, props.entry]);
318
+ // A resolver is consulted unless the block brings its own files and the resolver
319
+ // says (via `list()`) that it does not know this src.
320
+ const tryResolver = !!resolver && !(inlineFiles && (!src || (resolver.list ? !resolver.list().includes(src) : false)));
321
+ const hookResolver = tryResolver ? resolver : undefined;
322
+ const meta = useMeta(hookResolver, src);
323
+ const resolvedFiles = useFiles(hookResolver, src, near);
324
+ const component = useComponent(hookResolver, src, near);
325
+ const resolverFailed = meta.status === "error" || resolvedFiles.status === "error" || component.status === "error";
326
+ const mode = tryResolver && !(inlineFiles && resolverFailed) ? "resolver" : inlineFiles ? "inline" : "none";
327
+ const metaValue = mode === "resolver" && meta.status === "ready" ? meta.value : undefined;
328
+ const files = mode === "inline" ? { status: "ready", value: inlineFiles } : resolvedFiles;
329
+ const title = props.title ?? metaValue?.title ?? (src.split("/").pop() || "Demo");
330
+ const description = props.description ?? metaValue?.description;
331
+ const variants = props.variants?.length ? props.variants : metaValue?.variants;
332
+ const [chosenVariant, setVariant] = useState(undefined);
333
+ const variant = variants?.length
334
+ ? (variants.some((v) => v.id === chosenVariant) ? chosenVariant : variants[0].id)
335
+ : undefined;
336
+ const layoutPref = props.layout ?? metaValue?.layout ?? "auto";
337
+ const fileList = files.status === "ready" ? files.value : null;
338
+ const layout = layoutPref !== "auto" ? layoutPref : fileList ? (fileList.length > 1 ? "multi" : "single") : null;
339
+ const heightValue = props.height ?? metaValue?.height;
340
+ // Look: the tag's attributes (props) first, then `meta.json`.
341
+ const bleed = props.bleed ?? metaValue?.bleed;
342
+ const propSurface = typeof props.surface === "string" ? cssToSurface(props.surface) : props.surface;
343
+ const look = {
344
+ className: props.canvasClassName ?? metaValue?.className,
345
+ surface: propSurface ?? metaValue?.surface,
346
+ };
347
+ const variantsWidth = props.variantsWidth ?? metaValue?.variantsWidth;
348
+ const status = props.status ?? metaValue?.status;
349
+ const [fullscreen, setFullscreen] = useState(false);
350
+ const href = mode === "resolver" ? resolver?.href?.(src, variant ? { variant } : undefined) : undefined;
351
+ const baseId = useId();
352
+ const { runtime, dependencies } = runtimeOptions;
353
+ const preview = useMemo(() => {
354
+ if (mode !== "inline")
355
+ return { kind: "component", component };
356
+ if (props.streaming)
357
+ return { kind: "streaming" };
358
+ if (!runtime || !inlineFiles)
359
+ return { kind: "unavailable" };
360
+ const entry = inlineFiles[0].path;
361
+ return { kind: "runtime", render: (v) => runtime({ src, title, files: inlineFiles, entry, variant: v, dependencies }) };
362
+ }, [mode, component, props.streaming, runtime, inlineFiles, src, title, dependencies]);
363
+ const runnable = preview.kind !== "unavailable";
364
+ if (mode === "none") {
365
+ return (_jsxs("div", { className: "docs-source-ref", "data-docstream-demo": "", "data-demo-src": src, children: [_jsx(FileCode2, { width: 16, height: 16, "aria-hidden": "true" }), _jsx("span", { children: title }), _jsxs("code", { children: ["demo:", src] })] }));
366
+ }
367
+ const node = {
368
+ type: "demo",
369
+ src,
370
+ ...(props.title ? { title: props.title } : {}),
371
+ ...(props.description ? { description: props.description } : {}),
372
+ ...(props.height !== undefined ? { height: String(props.height) } : {}),
373
+ ...(props.layout ? { layout: props.layout } : {}),
374
+ ...(props.variants?.length ? { variants: props.variants } : {}),
375
+ ...(props.viewport ? { viewport: props.viewport } : {}),
376
+ ...(props.status ? { status: props.status } : {}),
377
+ ...(props.bleed !== undefined ? { bleed: props.bleed } : {}),
378
+ ...(props.canvasClassName ? { className: props.canvasClassName } : {}),
379
+ ...(props.surface ? { surface: typeof props.surface === "string" ? props.surface : surfaceToCss(props.surface) } : {}),
380
+ ...(props.variantsWidth !== undefined && props.variantsWidth !== "" ? { variantsWidth: String(props.variantsWidth) } : {}),
381
+ };
382
+ const copyDemo = () => inlineDemoMarkdown(node, fileList ?? [], metaValue);
383
+ const dialog = (_jsx(FullscreenDialog, { open: fullscreen, onClose: () => setFullscreen(false), title: title, src: src, variants: variants, variant: variant, onVariant: setVariant, preview: preview, bleed: bleed, look: look, variantsWidth: variantsWidth, href: href }));
384
+ const wrapper = (kind) => ["docs-demo", `docs-demo-${kind}`, props.className].filter(Boolean).join(" ");
385
+ if (layout === "single") {
386
+ const file = fileList?.[0];
387
+ return (_jsxs("section", { ref: ref, className: wrapper("single"), "data-docstream-demo": "", "data-demo-src": src, "data-demo-mode": mode, "aria-labelledby": `${baseId}-title`, children: [_jsxs("div", { className: "docs-react-demo", children: [_jsxs("header", { className: "docs-react-demo-header docs-demo-single-head", children: [_jsxs("span", { className: "docs-demo-heading", children: [_jsx("span", { id: `${baseId}-title`, className: "docs-demo-title", children: title }), description ? _jsx("span", { className: "docs-demo-desc", children: _jsx(InlineMarkdown, { text: description }) }) : null] }), _jsx("span", { className: "docs-demo-spacer" }), _jsx(StatusBadge, { status: status }), _jsx(VariantSwitch, { variants: variants, value: variant, onChange: setVariant, title: title, width: variantsWidth }), runnable ? _jsx(FullscreenButton, { onOpen: () => setFullscreen(true), title: title }) : null] }), runnable ? (_jsx("div", { className: "docs-react-demo-preview docs-demo-single-preview",
388
+ // A hint, not a cap: in-page components may grow past it (no height: size to content;
389
+ // hosts may set a floor with --docs-demo-preview-min-height). An iframe needs a real height.
390
+ style: preview.kind === "runtime"
391
+ ? { height: cssLength(heightValue, "360px") }
392
+ : heightValue === undefined || heightValue === ""
393
+ ? undefined
394
+ : { minHeight: cssLength(heightValue, "0px") }, children: near ? _jsx(DemoCanvas, { src: src, preview: preview, variant: variant, bleed: bleed, look: look }) : _jsx(Spinner, {}) })) : (_jsx(DemoCanvas, { src: src, preview: preview, variant: variant })), file ? (_jsx(CollapsibleCode, { code: file.content.replace(/\n+$/, ""), language: file.language ?? languageForPath(file.path) ?? null, copyLabel: `Copy ${file.path}`, ...(runnable ? {} : { collapsedLines: 12 }) })) : null] }), dialog] }));
395
+ }
396
+ return (_jsx(MultiFileViewer, { rootRef: ref, className: wrapper(layout ?? "pending"), baseId: baseId, mode: mode, src: src, title: title, description: description, variants: variants, variant: variant, onVariant: setVariant, files: files, preview: preview, near: near, height: cssLength(heightValue, "480px"), viewport: props.viewport, bleed: bleed, look: look, status: status, variantsWidth: variantsWidth, href: href, onFullscreen: () => setFullscreen(true), copyDemo: fileList?.length ? copyDemo : undefined, dialog: dialog }));
397
+ }
398
+ function MultiFileViewer({ rootRef, className, baseId, mode, src, title, description, variants, variant, onVariant, files, preview, near, height, viewport, bleed, look, status, variantsWidth, href, onFullscreen, copyDemo, dialog }) {
399
+ const runnable = preview.kind !== "unavailable";
400
+ // Nothing to preview without a runtime: open on the code rather than an empty frame.
401
+ const [tab, setTab] = useState(runnable ? "preview" : "code");
402
+ const [width, setWidth] = useState(() => VIEWPORTS.find((v) => v.id === viewport)?.width ?? null);
403
+ const [codeSeen, setCodeSeen] = useState(false);
404
+ useEffect(() => {
405
+ if (tab === "code")
406
+ setCodeSeen(true);
407
+ }, [tab]);
408
+ const activeViewport = width === null ? "desktop" : VIEWPORTS.find((v) => v.width === width)?.id;
409
+ return (_jsxs("section", { ref: rootRef, className: className, "data-docstream-demo": "", "data-demo-src": src, "data-demo-mode": mode, "aria-labelledby": `${baseId}-title`, children: [_jsxs("div", { className: "docs-demo-head", children: [_jsxs("div", { className: "docs-demo-heading", children: [_jsx("div", { id: `${baseId}-title`, className: "docs-demo-title", children: title }), description ? _jsx("p", { className: "docs-demo-desc", children: _jsx(InlineMarkdown, { text: description }) }) : null] }), _jsxs("div", { className: "docs-demo-tools", children: [_jsx(PillTabs, { options: [{ id: "preview", label: "Preview" }, { id: "code", label: "Code" }], value: tab, onChange: setTab, label: `${title} view`, tabId: (id) => `${baseId}-tab-${id}`, panelId: (id) => `${baseId}-panel-${id}` }), _jsx(StatusBadge, { status: status }), _jsx(VariantSwitch, { variants: variants, value: variant, onChange: onVariant, title: title, width: variantsWidth }), tab === "preview" && runnable ? (_jsx("div", { className: "docs-demo-viewports", role: "group", "aria-label": "Preview width", children: VIEWPORTS.map(({ id, label, width: w, Icon }) => (_jsx("button", { type: "button", className: "docs-demo-vp", "aria-pressed": activeViewport === id, "aria-label": w ? `${label} (${w}px)` : label, title: w ? `${label} · ${w}px` : label, onClick: () => setWidth(w), children: _jsx(Icon, { width: 15, height: 15, "aria-hidden": "true" }) }, id))) })) : null, copyDemo ? _jsx(CopyButton, { text: copyDemo, label: "Copy demo as Markdown", className: "docs-demo-icon-btn" }) : null, runnable ? _jsx(FullscreenButton, { onOpen: onFullscreen, title: title }) : null, _jsx(NewTabLink, { href: href, title: title })] })] }), _jsxs("div", { className: "docs-demo-body", children: [_jsx("div", { id: `${baseId}-panel-preview`, role: "tabpanel", "aria-labelledby": `${baseId}-tab-preview`, hidden: tab !== "preview", children: runnable ? (_jsx(PreviewStage, { width: width, onResize: setWidth, height: height, children: near ? _jsx(DemoCanvas, { src: src, preview: preview, variant: variant, bleed: bleed, look: look }) : _jsx(Spinner, {}) })) : (_jsx(DemoCanvas, { src: src, preview: preview, variant: variant })) }), _jsxs("div", { id: `${baseId}-panel-code`, role: "tabpanel", "aria-labelledby": `${baseId}-tab-code`, hidden: tab !== "code", children: [!runnable && tab === "code" ? _jsx(DemoCanvas, { src: src, preview: preview, variant: variant }) : null, codeSeen || tab === "code" ? (files.status === "ready" ? (_jsx(CodeView, { src: src, files: files.value, height: height, id: baseId })) : files.status === "error" ? (_jsx(DemoError, { message: `${src}: ${files.error.message}` })) : (_jsx("div", { className: "docs-demo-code", style: { height }, children: _jsx(Spinner, { label: "Loading files" }) }))) : null] })] }), dialog] }));
410
+ }
411
+ /** Renders a parsed `{% demo %}` block with the resolver and runtime from context. */
412
+ export function DemoBlock({ node }) {
413
+ return (_jsx(DemoViewer, { src: node.src, title: node.title, description: node.description, height: node.height, layout: node.layout, variants: node.variants, viewport: node.viewport, files: node.files, entry: node.entry, streaming: node.open, status: node.status, bleed: node.bleed, canvasClassName: node.className, surface: node.surface, variantsWidth: node.variantsWidth }));
414
+ }
415
+ /** One demo filling the viewport, for a `?demo=<src>` deep link the host routes. */
416
+ export function DemoFullscreen({ src, resolver: override, variant, className, style }) {
417
+ const resolver = useDemoResolver(override);
418
+ const meta = useMeta(resolver, src);
419
+ const component = useComponent(resolver, src, true);
420
+ const metaValue = meta.status === "ready" ? meta.value : undefined;
421
+ const chosen = variant ?? metaValue?.variants?.[0]?.id;
422
+ if (!resolver)
423
+ return _jsx(DemoError, { message: `No demo resolver for ${src}` });
424
+ return (_jsx("div", { className: className ? `docs-demo-fullscreen ${className}` : "docs-demo-fullscreen", style: style, "data-demo-src": src, children: _jsx(DemoCanvas, { src: src, preview: { kind: "component", component }, variant: chosen, bleed: metaValue?.bleed, look: metaValue }) }));
425
+ }
426
+ //# sourceMappingURL=DemoViewer.js.map